package es.us.idinfor.rmplugin.model;

import java.util.List;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;

import com.rapidminer.example.Attribute;
import com.rapidminer.example.Example;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.example.Statistics;

import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.learner.SimplePredictionModel;

import com.rapidminer.tools.container.Tupel;

import es.us.idinfor.rmplugin.example.ExamplesFactory;

/**
 * The LAMDA Gaussian model. For classification problems, the class with the maximum value of GAD is choosen.
 *
 * @author Miguel Angel Alvarez de la Concepcion
 * @version 1.0
 */
public class LAMDAGaussianModel extends SimplePredictionModel {
	private static final long serialVersionUID = -8736451062213945723L;
	
	private ExampleSet es;
	private double alpha;
	private Table<String, Attribute, Double> meanTable;
	private Table<String, Attribute, Double> varianceTable;

	/**
	 * Default constructor.
	 * 
	 * @param exampleSet The example set.
	 * @param value The alpha value.
	 * @throws OperatorException It is thrown when the initialization has failed.
	 */
	public LAMDAGaussianModel(ExampleSet exampleSet, double value) throws OperatorException {
		super(exampleSet);
		
		es = exampleSet;
		alpha = value;
		meanTable = HashBasedTable.create();
		varianceTable = HashBasedTable.create();
		
		initialize();
	}

	/**
	 * @see SimplePredictionModel#predict(Example)
	 */
	public double predict(Example example) throws OperatorException {
		double res = Double.NaN;
		double max = Double.NaN;
		double min = Double.NaN;
		double mean = Double.NaN;
		double variance = Double.NaN;
		double value = Double.NaN;
		double exponent = Double.NaN;
		double MADValue = Double.NaN;
		double GADValue = Double.NaN;
		Tupel<String, Double> aux = null;
		List<String> classNames = es.getAttributes().getLabel().getMapping().getValues();
		
		for(String name: classNames) {
			max = Double.NaN;
			min = Double.NaN;
			
			for(Attribute att: es.getAttributes()) {
				value = example.getValue(att);
				mean = meanTable.get(name, att);
				variance = varianceTable.get(name, att);
				exponent = -1 * (1 / (2 * variance)) * Math.pow(value - mean, 2);
				MADValue = Math.pow(Math.E, exponent);
				
				if(Double.isNaN(max) || (max < MADValue)) {
					max = MADValue;
				}
				
				if(Double.isNaN(min) || (min > MADValue)) {
					min = MADValue;
				}
			}
			
			GADValue = (alpha * min) + ((1 - alpha) * max);
			
			if((aux == null) || aux.getSecond() < GADValue) {
				aux = new Tupel<String, Double>(name, GADValue);
			}
		}
		
		res = es.getAttributes().getLabel().getMapping().getIndex(aux.getFirst());
		
		classNames = null;
		aux = null;
		
		return res;
	}
	
	/**
	 * Initializes the MAD table with the mean and variance values for each couple of attribute and class value.
	 * 
	 * @throws OperatorException  It is thrown when the initialization has failed.
	 */
	private void initialize() throws OperatorException {
		List<String> classNames = es.getAttributes().getLabel().getMapping().getValues();
		ExampleSet subset = null;
		
		for(String name: classNames) {
			subset = ExamplesFactory.getSubSet(es, es.getAttributes().getLabel().getName(), name);
			subset.recalculateAllAttributeStatistics();
			
			for(Attribute att: es.getAttributes()) {
				meanTable.put(name, att, subset.getStatistics(att, Statistics.AVERAGE));
				varianceTable.put(name, att, subset.getStatistics(att, Statistics.VARIANCE));
			}
		}
		
		subset = null;
		classNames = null;
	}
	
	protected void finalize() throws Throwable {
		super.finalize();
		
		es = null;
		meanTable = null;
		varianceTable = null;
	}
}
